defer 陷阱

defer 陷阱

defer和函数返回值

defer中如果引用了函数的返回值,则因引用形式不同会导致不同的结果。

下面来看三个示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

func f1() (r int) {
defer func(r int) {
r = r + 5
}(r)
return 1
}

func f2() (r int) {
defer func() {
r++
}()
return 0
}

func f3() (r int) {
t := 5
defer func() {
t = t + 5
}()
return t
}

func TestDefer(t *testing.T) {
t.Log("f1 run result : ", f1())
t.Log("f2 run result : ", f2())
t.Log("f3 run result : ", f3())
}

想想看最终会输出什么呢?

实际的执行结果如下,是不是和预想的有差异呢?

1
2
3
4
5
6
=== RUN   TestDefer
--- PASS: TestDefer (0.00s)
defer_test.go:44: f1 run result : 1
defer_test.go:45: f2 run result : 1
defer_test.go:46: f3 run result : 5
PASS

首先来看看f1()内部的执行顺序

1
2
3
4
5
6
7
8
9
func f1() (r int) {         // 1. 要执行f1 初始化返回值r为零值  相当于 var r int = 0   
defer func(r int) { // 2. 注册defer要执行的函数 此时已经对此处注册的已经将r = 0 以值拷贝的形式将参数传入
r = r + 5 // 先不执行
}(r)
return 1 // 3. 对r进行赋值 r = 1
// 4. 执行注册defer函数 此时defer内部的r2(便于区分将外部的r称为r1,此处为r2)已经是注册defer时的值拷贝,此时 r2 = 0,r1 = 1;
// 5. 执行r = r + 5 此时r2 = 5, r1 = 1;
// 6 .RET 真正执行return r1 因此f1() = 1
}

再看f2()的执行过程

1
2
3
4
5
6
7
8
func f2() (r int) {   //  初始化返回值r为零值  相当于 var r int = 0   ,此时r=0
defer func() { // 注册defer函数
r++
}()
return 0 // 对返回值r进行赋值 r=0
// 执行defer函数 由于defer注册的时候没有传值,因此直接取到了返回值r=0, 并对r进行r++运算 得到 r = 1
// RET 执行return r 因此f2() = 1
}

最后看下f3()的执行过程

1
2
3
4
5
6
7
8
9
func f3() (r int) {   //  初始化返回值r为零值  相当于 var r int = 0   ,此时r=0
t := 5 // 声明变量t = 5 ,此时返回值r = 0
defer func() { // 注册defer函数
t = t + 5
}()
return t // 对返回值r进行赋值 r = t 因此 r = 5 t= 5
// 执行defer内部 t = t + 5 得到t = 10 未对r进行操作 因此r未变 r = 5
// RET 执行return r 因此返回值为5
}